/*
 * MyUnitTester
 * 
 * Version information:1.0
 *
 * Date: 19/11/2012
 */
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.swing.JFrame;
import javax.swing.JTextArea;

/**
 * @author dv11rlm
 */
public class MyUnitTester implements Runnable {

    // Method object used for repeated setup.
    private Method setUp;
    // Method object used for repeated teardown.
    private Method tearDown;
    // counter that counts the number of tests run.
    private int testCounter;
    // counter that counts test that causes exception.
    private int testExceptionCounter;
    // name of class that will be tested.
    private String className;
    // class object of the class we want to tests.
    private Class<?> TestClass;
    // A queue which appends messages to selected JTextArea
    private MessageQueue myQueue;

    /**
     * main function that will startup
     * 
     * @param args
     */
    public static void main(String[] args) {
	javax.swing.SwingUtilities.invokeLater(new Runnable() {
	    public void run() {
		createAndShowGUI();
	    }
	});
    }

    private static void createAndShowGUI() {
	JFrame mainWindow = new JFrame("ClassTester");
	mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	mainWindow.setSize(800, 640);
	mainWindow.add(new MyUnitTesterGUI());
	mainWindow.setResizable(false);
	mainWindow.pack();
	mainWindow.setVisible(true);
    }

    /**
     * Initialize variables needed to run class.
     * 
     * @param className
     * @param textArea
     */
    public MyUnitTester(String className, JTextArea textArea) {
	this.className = className;
	testExceptionCounter = 0;
	testCounter = 0;
	myQueue = new MessageQueue(textArea);
    }

    /**
     * Function that test if class exists and is valid for testing.
     */
    @Override
    public void run() {
	try {
	    TestClass = Class.forName(className);
	    Class<?>[] interfaces = TestClass.getInterfaces();
	    boolean correctInterface = false;
	    for (Class<?> classInterface : interfaces) {
		if ("TestClass".equals(classInterface.getName())) {
		    runClass(TestClass);
		    correctInterface = true;
		}
	    }

	    if (!correctInterface) {
		new UserFeedback(className
			+ " don't implement TestClass interface so "
			+ "no tests can be run.", UserFeedback.WARNING_DIALOG);
	    }
	} catch (ClassNotFoundException e) {
	    new UserFeedback("Class not found", UserFeedback.DEFAULT_DIALOG);
	}

	myQueue.indicateShutdownMessageQueue();
    }

    /**
     * Method that initialize setup, teardown, constructor.
     * 
     * @param TestClass
     */
    private void runClass(Class<?> TestClass) {

	try {
	    // checks if class have a constructor with 0 arguments.
	    Object instanceObject = initializeConstructor(TestClass);
	    if (instanceObject == null) {
		new UserFeedback("No valid Constructors could be found.",
			UserFeedback.ERROR_DIALOG);
		return;
	    }

	    setUp = getMethod("setUp");
	    tearDown = getMethod("tearDown");
	    Method[] methodList = TestClass.getDeclaredMethods();
	    int successfulTests = 0;

	    for (Method method : methodList) {
		method.setAccessible(true);
		if (!method.getName().startsWith("test")
			|| (method.getGenericReturnType() != boolean.class)
			|| method.isVarArgs()) {
		    continue;
		}

		testCounter++;
		successfulTests = runTest(instanceObject, successfulTests,
			method);
	    }

	    myQueue.addResultToQueue("Successful Tests: " + successfulTests);
	    myQueue.addResultToQueue("Fail tests: "
		    + (testCounter - successfulTests - testExceptionCounter));
	    myQueue.addResultToQueue("Fail Tests: " + testExceptionCounter
		    + " Caused by exception");
	} catch (SecurityException e) {
	    new UserFeedback("SecurityException: " + e.getCause(),
		    UserFeedback.ERROR_DIALOG);
	} catch (NoSuchMethodException e) {
	    new UserFeedback("Tried to use a use a function that is not used:"
		    + e.getCause(), UserFeedback.ERROR_DIALOG);
	}

    }

    private int runTest(Object instanceObject, int successfulTests,
	    Method method) {
	try {
	    method.setAccessible(true);

	    // setUp
	    if (setUp != null) {
		setUp.invoke(instanceObject);
	    }

	    // runTest
	    if (((Boolean) method.invoke(instanceObject))) {
		myQueue.addResultToQueue(method.getName() + ": SUCCESS");
		successfulTests++;
	    } else {
		myQueue.addResultToQueue(method.getName() + ": FAIL");
	    }

	    // tearDown
	    if (tearDown != null) {
		tearDown.invoke(instanceObject);
	    }

	} catch (InvocationTargetException e) {
	    failedTestException((method.getName() + ": "), e.getCause()
		    .toString());
	} catch (IllegalArgumentException e) {
	    failedTestException((method.getName() + ": "),
		    " java.lang.IllegalArgumentException");
	} catch (IllegalAccessException e) {
	    failedTestException((method.getName() + ": "),
		    " java.lang.IllegalAccessException\n");
	}
	return successfulTests;
    }

    /**
     * return a method object from the previously given class. if
     * exception happens null will return.
     * 
     * @param methodName
     * @return
     */
    private Method getMethod(String methodName) {
	try {
	    return TestClass.getDeclaredMethod(methodName);
	} catch (NoSuchMethodException e) {
	    new UserFeedback("Tried to use: " + methodName
		    + " but method can't be found or\n method "
		    + "requires parameters", UserFeedback.ERROR_DIALOG);
	}
	return null;

    }

    /**
     * Will initialize a object with a instance of class, if class
     * have a constructor.
     * 
     * @param testClass2
     * @return Object
     * @throws SecurityException
     * @throws NoSuchMethodException
     */
    private Object initializeConstructor(Class<?> testClass2)
	    throws SecurityException, NoSuchMethodException {
	Object instanceObject;
	if ((instanceObject = getConstrutor(TestClass)) == null) {
	    return null;
	}
	if (!hasConstructor(TestClass)) {
	    return null;
	}

	return instanceObject;
    }

    /**
     * check if a constructor is valid, if so return the instance of
     * it.
     * 
     * @param TestClass
     * @return
     */
    private Object getConstrutor(Class<?> TestClass) {
	Object constructorInstance = null;
	for (Constructor<?> testClassDeclaredConstructor : TestClass
		.getDeclaredConstructors()) {

	    testClassDeclaredConstructor.setAccessible(true);

	    try {
		constructorInstance = testClassDeclaredConstructor
			.newInstance();
	    } catch (IllegalArgumentException e) {
		/*
		 * Constructor takes argument, this will be handled in
		 * runClass function. This is to allow multiple
		 * constructors to be tested.
		 */
	    } catch (InstantiationException e) {
		new UserFeedback("Class: " + TestClass.getSimpleName()
			+ "Couldn't be initiated", UserFeedback.ERROR_DIALOG);
		return null;
	    } catch (IllegalAccessException e) {
		new UserFeedback("Class: " + TestClass.getSimpleName()
			+ "Couldn't be accessed\n", UserFeedback.ERROR_DIALOG);
		return null;
	    } catch (InvocationTargetException e) {
		new UserFeedback("Class: " + TestClass.getSimpleName()
			+ "Couldn't be found", UserFeedback.ERROR_DIALOG);
		return null;
	    }
	    if (constructorInstance != null) {
		break;
	    }
	}
	return constructorInstance;
    }

    /**
     * Check if the class object have a constructor without arguments.
     * 
     * @param TestClass
     * @return
     * @throws SecurityException
     * @throws NoSuchMethodException
     */
    private boolean hasConstructor(Class<?> TestClass)
	    throws SecurityException, NoSuchMethodException {
	Constructor<?>[] allConstructors = TestClass.getDeclaredConstructors();
	for (Constructor<?> currentConstructor : allConstructors) {
	    if (!currentConstructor.isVarArgs()) {
		return true;
	    }
	}
	return false;
    }

    /**
     * Send exception-output to message queue and increment exception
     * counter.
     * 
     * @param functionName
     * @param cause
     */
    public void failedTestException(String functionName, String cause) {
	testExceptionCounter++;
	myQueue.addResultToQueue(functionName + "FAIL Generated a " + cause);
    }
}
